﻿#include "memory.hpp"

extern std::map<uint64_t, Atomic<std::array<uint8_t, 64>>> global_ram;

CacheLine &CacheBlock::operator[](Index msg)
{
	uint32_t tag = std::get<0>(msg);
	if (idx.find(tag) != idx.end())
	{
		if (lines.size() > 1)
		{
			auto temp = *(idx[tag]);
			lines.push_front(temp);
			lines.erase(idx[tag]);
			idx[tag] = lines.begin();
		}
	}
	else
	{
		lines.push_front({tag, {{}, MESIMETA::Invalid}});
		idx[tag] = lines.begin();
		if (lines.size() > size)
		{
			auto it = lines.back();
			uint64_t addr = tag;
			addr <<= std::get<2>(msg);
			addr |= std::get<1>(msg);
			global_ram[addr].w_lock();
			global_ram[addr].get() = it.second.data;
			global_ram[addr].w_unlock();
			lines.pop_back();
			idx.erase(it.first);
		}
	}
	return lines.begin()->second;
}
void CacheBlock::clear(uint16_t index,uint8_t offset){
	for(auto &it:lines){
		if (it.second.meta != MESIMETA::Modified)
			return;
		uint64_t addr = it.first;
		addr <<= offset;
		addr |= index;
		global_ram[addr].w_lock();
		global_ram[addr].get() = it.second.data;
		global_ram[addr].w_unlock();
	}
	lines.clear();
	idx.clear();
}

void Cache::readIns(uint64_t addr, uint64_t *data)
{
	uint64_t blockID = addr >> 6;
	uint16_t idx = blockID & 0x3ff;
	uint64_t tag = blockID >> 10;
	if (l1i.find(idx) != l1i.end()) // l1i search
	{
		auto it = l1i[idx];
		if (it.first == tag)
		{
			// Cache hit
			if (it.second.meta == MESIMETA::Invalid)
				goto l1i_miss;
			memcpy(data, it.second() + (addr & 0x3f), 4);
			return;
		}
		else
			goto l1i_miss;
	}
	else
	{
		l1i[idx] = {tag, CacheLine()};
	l1i_miss:
		idx = blockID & 0x3fff;
		tag = blockID >> 14;
		if (l2.find(idx) != l2.end())
		{
			auto it = l2[idx];
			CacheLine &ptr = it[{tag, idx, 14}];
			if (ptr.meta & MESIMETA::Invalid)
				goto l2_miss;
			memcpy(data, ptr() + (addr & 0x3f), 4);
			idx = blockID & 0x3ff;
			tag = blockID >> 10;
			l1i[idx] = {tag, ptr};
			return;
		}
		else
		{
			l2[idx] = CacheBlock();
			l2[idx].size = 2;
		l2_miss:
			CacheLine &ptr = l2[idx][{tag, idx, 14}];
			global_ram[blockID].r_lock();
			ptr = {global_ram[blockID].get(), MESIMETA::Exclusive};
			global_ram[blockID].r_unlock();
			memcpy(data, ptr() + (addr & 0x3f), 4);
			return;
		}
	}
}
void Cache::read(uint64_t addr, uint64_t *data, uint8_t size)
{
	*data = 0;
	uint64_t blockID = addr >> 6;
	uint16_t idx = blockID & 0x3ff;
	uint64_t tag = blockID >> 10;
	if (l1d.find(idx) != l1d.end())
	{
		auto &it = l1d[idx][{tag, idx, 10}];
		if (it.meta == MESIMETA::Invalid)
			goto l1d_miss;
		memcpy(data, it() + (addr & 0x3f), size);
		return;
	}
	else
	{
		l1d[idx] = CacheBlock();
		l1d[idx].size = 4;
	l1d_miss:
		idx = blockID & 0x3fff;
		tag = blockID >> 14;
		if (l2.find(idx) != l2.end())
		{
			auto &it = l2[idx][{tag, idx, 14}];
			if (it.meta == MESIMETA::Invalid)
				goto l2_miss;
			memcpy(data, it() + (addr & 0x3f), size);
			auto &ptr = l1d[blockID & 0x3ff][{blockID >> 10, blockID & 0x3ff, 10}];
			std::swap(ptr, it);
			return;
		}
		else
		{
			l2[idx] = CacheBlock();
			l2[idx].size = 2;
		l2_miss:
			auto &ptr = l2[idx][{tag, idx, 14}];
			global_ram[blockID].r_lock();
			ptr = {global_ram[blockID].get(), MESIMETA::Exclusive};
			global_ram[blockID].r_unlock();
			memcpy(data, ptr() + (addr & 0x3f), size);
			return;
		}
	}
}
void Cache::write(uint64_t addr, uint64_t *data, uint8_t size)
{
	uint64_t blockID = addr >> 6;
	uint16_t idx = blockID & 0x3ff;
	uint64_t tag = blockID >> 10;
	if (l1d.find(idx) != l1d.end())
	{
		auto &it = l1d[idx][{tag, idx, 10}];
		if (it.meta & MESIMETA::Invalid)
			goto l1d_miss;
		it.meta = MESIMETA::Modified;
		memcpy(it() + (addr & 0x3f), data, size);
		return;
	}
	else
	{
		l1d[idx] = CacheBlock();
		l1d[idx].size = 4;
	l1d_miss:
		idx = blockID & 0x3fff;
		tag = blockID >> 14;
		if (l2.find(idx) != l2.end())
		{
			auto it = l2[idx][{tag, idx, 14}];
			if (it.meta & MESIMETA::Invalid)
				goto l2_miss;
			it.meta = MESIMETA::Modified;
			memcpy(it() + (addr & 0x3f), data, size);
			auto ptr = l1d[blockID & 0x3ff][{blockID >> 10, blockID & 0x3ff, 10}];
			std::swap(ptr, it);
			return;
		}
		else
		{
			l2[idx] = CacheBlock();
			l2[idx].size = 2;
		l2_miss:
			auto &ptr = l2[idx][{tag, idx, 14}];
			global_ram[blockID].r_lock();
			ptr = {global_ram[blockID].get(), MESIMETA::Modified};
			global_ram[blockID].r_unlock();
			memcpy(ptr() + (addr & 0x3f), data, size);
			return;
		}
	}
}
void Cache::operator()(uint64_t addr, uint64_t *data, uint8_t size, uint8_t access)
{
	// 单核测试版本，暂时不进行多核一致性检查
	// refresh();
	if (access & R)
		(access & X) ? readIns(addr, data) : read(addr, data, size);
	else if (access & W)
		write(addr, data, size);
	else
		throw "Invalid access type";
}
void Cache::shutdown()
{
	for (auto &[idx, it] : l1d)
		it.clear(idx, 10);
	for (auto &[idx, it] : l2)
		it.clear(idx, 14);
}